import { BufferAttribute, BufferGeometry, MathUtils, Plane, Ray, Vector2, Vector3, Vector4 } from "three";
import Config from "../application/constants/Config";
import Const from "../application/constants/Const";
import GPSUtils from "./GPSUtils";
import { Point } from "../renderExtension/types/Point";
import Pixel from "../renderExtension/types/Pixel";
import { App } from "../application/App";

export default class GeoUtils {
    public static getResolution(scale: number) {
        return scale / Config.Map.IPU / Config.Map.DPI;
    }

    public static getR16(row: number, lower: boolean = true) {
        let r16 = lower ? row.toString(16) : row.toString(16).toUpperCase();
        return "R" + "00000000".substring(0, 8 - r16.length) + r16;
    }
    public static getC16(column: number, lower: boolean = true) {
        let c16 = lower ? column.toString(16) : column.toString(16).toUpperCase();
        return "C" + "00000000".substring(0, 8 - c16.length) + c16;
    }

    public static getR10(y: number, zoom: number) {
        let r = GeoUtils.getMapResolution(zoom);
        return parseInt(((Config.Map.DatumPoint[1] - y) / (r * Config.Map.TileSize)).toString());
    }
    public static getC10(x: number, zoom: number) {
        let r = GeoUtils.getMapResolution(zoom);
        if((x - Config.Map.DatumPoint[0]) > 0 )
        {
            return parseInt(((x - Config.Map.DatumPoint[0]) / (r * Config.Map.TileSize)).toString());
        }
        else
        {
            return parseInt(( Math.floor( (x - Config.Map.DatumPoint[0]) / (r * Config.Map.TileSize)) ).toString());
        }       
    }

    private static getCustomResolution(level: number) {
        let levelConfig = Config.Map.SatelliteConfigMap.get(level);
        if (levelConfig) {
            return levelConfig.Resolution;;
        } else {
            levelConfig = Config.Map.SatelliteConfigMap.get(Config.Map.SatelliteConfigMap.size - 1);
            return levelConfig.Resolution;
        }
    }

    private static getMapResolution(zoom: number){
        let res;
        if(Config.Map.SatelliteConfigMap.size > 0){
            res = GeoUtils.getCustomResolution(zoom);
        }else{
            res = GeoUtils.getSatelliteResolution(zoom);   
        }   
        return res;
    }
    
    /** 计算列中心坐标 */
    public static getCDevX(column: number, zoom: number, offset: number = 0.5) {
        let res = GeoUtils.getMapResolution(zoom);
        return Config.Map.DatumPoint[0] + (column + offset) * Config.Map.TileSize * res;
    }
    /** 计算行中心坐标 */
    public static getRDevY(row: number, zoom: number, offset: number = 0.5) {
        let res = GeoUtils.getMapResolution(zoom);
        return Config.Map.DatumPoint[1] - (row + offset) * Config.Map.TileSize * res;
    }

    //通过像素坐标获取卫星瓦片地图的行号
    public static getSatelliteRow(y: number, zoom: number) {
        let resolution = GeoUtils.getSatelliteResolution(zoom);
        let lat = (2.0 - y / 268435456.0) * Math.PI;
        lat = Math.pow(Math.E, lat);
        lat = Math.asin((lat - 1.0) / (lat + 1.0));
        lat = 180.0 * lat / Math.PI;

        let mercatorPosition = GPSUtils.P84ToWeb_Y(lat);

        let satelRow = Math.floor((Config.Map.DatumPoint[1] - mercatorPosition) / (256 * resolution));

        return satelRow;
    }

    //通过像素坐标获取卫星瓦片地图的列号
    public static getSatelliteCol(x: number, zoom: number) {
        let lng = x * 360.0 - 180.0;
        let mercatorPosition = GPSUtils.P84ToWeb_X(lng);

        let resolution = GeoUtils.getSatelliteResolution(zoom);
        let satelCol = Math.floor((mercatorPosition - Config.Map.DatumPoint[0]) / (256 * resolution));

        return satelCol;
    }

    //根据卫星瓦片地图的行号，计算出像素坐标Y
    public static getSatelliteRY(row: number, zoom: number, offset: number = 0.0) {
        let resolution = GeoUtils.getSatelliteResolution(zoom);
        let mercatorPosition = Config.Map.DatumPoint[1] - (256 * resolution) * (row + offset);
        let lat = GPSUtils.WebtoP84_Y(mercatorPosition);

        if (lat > 85.0)
            lat = 85.0;
        if (lat < -85.0)
            lat = -85.0;
        let phi = Math.sin(Math.PI * lat / 180.0);
        phi = 0.5 * Math.log((1 + phi) / (1 - phi));
        let y = ((1 - phi / Math.PI) / 2);
        let tileY = y;
        return tileY;
    }

    //根据卫星瓦片地图的列号，计算出像素坐标X
    public static getSatelliteCX(column: number, zoom: number, offset: number = 0.0) {
        let resolution = GeoUtils.getSatelliteResolution(zoom);
        let mercatorPosition = (column + offset) * ((256 * resolution)) + Config.Map.DatumPoint[0];

        let lng = GPSUtils.WebtoP84_X(mercatorPosition);
        let originX = (lng + 180.0) / 360;
        return originX;
    }

    private static getSatelliteResolution(zoom: number) {
        return GeoUtils.getResolution(Config.Map.ScaleList[zoom]);
    }

    public static getCurrentTile(map, lntlat) {
        let zoom = map.getZoom();

        // let v3 = new LngLat(lng, lat, 0, Const.EPSG.WGS84).toVector3();
        let v3 = lntlat.toVector3();
        let x = v3.x;
        let y = v3.y;
        var tilewidth = 1073741824 / Math.pow(2, zoom);

        var r = Math.floor((1073741824 - y) / tilewidth);
        var c = Math.floor(x / tilewidth);

        let tile = { x: c, y: r, z: zoom };

        return tile;
    }

    public static WebtoP84(x: number, y: number) {
        return { x: this.WebtoP84_X(x), y: this.WebtoP84_Y(y) };
    }
    public static P84toWeb(x: number, y: number) {
        return { x: this.P84ToWeb_X(x), y: this.P84ToWeb_Y(y) };
    }
    public static WebtoP84_Y(y: number) {
        y = y / 111319.4907777778;
        return 180 / Math.PI * (2 * Math.atan(Math.exp(y * Math.PI / 180)) - Math.PI / 2);
    }
    public static WebtoP84_X(x: number) {
        return x / 111319.4907777778;
    }
    public static P84ToWeb_Y(y: number) {
        y = Math.log(Math.tan((90 + y) * Math.PI / 360)) / (Math.PI / 180);
        y = y * 111319.4907777778;
        return y;
    }
    public static P84ToWeb_X(x: number) {
        return x * 111319.4907777778;
    }


    public static getTileWidth(mapLevel) {
        let globalMapWidth = 40075016.685568; //web墨卡托地图的长宽
        // double halfMapWidth = globalMapWidth / 2.0;
        let tileRowCount = Math.pow(2, mapLevel); //当前层级横向和纵向瓦片的数量
        let tileWidth = (globalMapWidth / tileRowCount);
        return tileWidth;
    }

    /**将3dtiles的直角椭球体坐标转换到经纬度坐标 */
    public static threedtilePointToWGS(x: number, y: number, z: number) {
        //return new Point(x, y, z, Const.EPSGType.EPSGWeb);
        var wgs84OneOverRadii = new Vector3(
            1.0 / 6378137.0,
            1.0 / 6378137.0,
            1.0 / 6356752.3142451793
        );

        var wgs84OneOverRadiiSquared = new Vector3(
            1.0 / (6378137.0 * 6378137.0),
            1.0 / (6378137.0 * 6378137.0),
            1.0 / (6356752.3142451793 * 6356752.3142451793)
        );

        var wgs84CenterToleranceSquared = 0.1;
        var oneOverRadii = wgs84OneOverRadii;
        var oneOverRadiiSquared = wgs84OneOverRadiiSquared;
        var centerToleranceSquared = wgs84CenterToleranceSquared;

        var cartesian = new Vector3(x, y, z);

        //`cartesian is required.` is thrown from scaleToGeodeticSurface
        var p = GeoUtils.scaleToGeodeticSurface(
            cartesian.clone(),
            oneOverRadii.clone(),
            oneOverRadiiSquared.clone(),
            centerToleranceSquared
        );

        if (!p) {
            return undefined;
        }
        let originPoint = p.clone();
        var n = p.multiply(oneOverRadiiSquared);
        n = n.normalize();

        var h = cartesian.clone().sub(originPoint);

        var longitude = MathUtils.radToDeg(Math.atan2(n.y, n.x));
        var latitude = MathUtils.radToDeg(Math.asin(n.z));

        var dotValue = h.dot(cartesian);
        if (dotValue > 0) {
            dotValue = 1;
        } else if (dotValue < 0) {
            dotValue = -1;
        } else if (dotValue == 0) {
            dotValue = 0;
        }
        var height = dotValue * h.length();

        return new Point(longitude, latitude, height, Const.EPSGType.EPSG84);
    }

    //对3dtiles的直接椭球体坐标做处理
    private static scaleToGeodeticSurface(
        cartesian: Vector3,
        oneOverRadii,
        oneOverRadiiSquared,
        centerToleranceSquared
    ) {
        //>>includeStart('debug', pragmas.debug);
        if (!cartesian) {
            throw new Error("cartesian is required.");
        }
        if (!oneOverRadii) {
            throw new Error("oneOverRadii is required.");
        }
        if (!oneOverRadiiSquared) {
            throw new Error("oneOverRadiiSquared is required.");
        }
        if (!centerToleranceSquared) {
            throw new Error("centerToleranceSquared is required.");
        }
        //>>includeEnd('debug');

        var positionX = cartesian.x;
        var positionY = cartesian.y;
        var positionZ = cartesian.z;

        var oneOverRadiiX = oneOverRadii.x;
        var oneOverRadiiY = oneOverRadii.y;
        var oneOverRadiiZ = oneOverRadii.z;

        var x2 = positionX * positionX * oneOverRadiiX * oneOverRadiiX;
        var y2 = positionY * positionY * oneOverRadiiY * oneOverRadiiY;
        var z2 = positionZ * positionZ * oneOverRadiiZ * oneOverRadiiZ;

        // Compute the squared ellipsoid norm.
        var squaredNorm = x2 + y2 + z2;
        var ratio = Math.sqrt(1.0 / squaredNorm);

        var scaleToGeodeticSurfaceGradient = new Vector3();
        var scaleToGeodeticSurfaceIntersection = new Vector3();

        // As an initial approximation, assume that the radial intersection is the projection point.
        var intersection = cartesian.multiplyScalar(ratio);

        // If the position is near the center, the iteration will not converge.
        if (squaredNorm < centerToleranceSquared) {
            return !isFinite(ratio)
                ? undefined
                : intersection.clone();
        }

        var oneOverRadiiSquaredX = oneOverRadiiSquared.x;
        var oneOverRadiiSquaredY = oneOverRadiiSquared.y;
        var oneOverRadiiSquaredZ = oneOverRadiiSquared.z;

        // Use the gradient at the intersection point in place of the true unit normal.
        // The difference in magnitude will be absorbed in the multiplier.
        var gradient = scaleToGeodeticSurfaceGradient;
        gradient.x = intersection.x * oneOverRadiiSquaredX * 2.0;
        gradient.y = intersection.y * oneOverRadiiSquaredY * 2.0;
        gradient.z = intersection.z * oneOverRadiiSquaredZ * 2.0;

        // Compute the initial guess at the normal vector multiplier, lambda.
        var lambda =
            ((1.0 - ratio) * cartesian.length()) /
            (0.5 * gradient.length());
        var correction = 0.0;

        var func;
        var denominator;
        var xMultiplier;
        var yMultiplier;
        var zMultiplier;
        var xMultiplier2;
        var yMultiplier2;
        var zMultiplier2;
        var xMultiplier3;
        var yMultiplier3;
        var zMultiplier3;
        var EPSILON12 = 0.000000000001;

        do {
            lambda -= correction;

            xMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredX);
            yMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredY);
            zMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredZ);

            xMultiplier2 = xMultiplier * xMultiplier;
            yMultiplier2 = yMultiplier * yMultiplier;
            zMultiplier2 = zMultiplier * zMultiplier;

            xMultiplier3 = xMultiplier2 * xMultiplier;
            yMultiplier3 = yMultiplier2 * yMultiplier;
            zMultiplier3 = zMultiplier2 * zMultiplier;

            func = x2 * xMultiplier2 + y2 * yMultiplier2 + z2 * zMultiplier2 - 1.0;

            // "denominator" here refers to the use of this expression in the velocity and acceleration
            // computations in the sections to follow.
            denominator =
                x2 * xMultiplier3 * oneOverRadiiSquaredX +
                y2 * yMultiplier3 * oneOverRadiiSquaredY +
                z2 * zMultiplier3 * oneOverRadiiSquaredZ;

            var derivative = -2.0 * denominator;

            correction = func / derivative;
        } while (Math.abs(func) > EPSILON12);

        return new Vector3(
            positionX * xMultiplier,
            positionY * yMultiplier,
            positionZ * zMultiplier
        );
    }

    /**
     * 经纬度坐标转换到直角椭球体坐标系
     */
    public static WGSTothreedtilePoint(lnglatPoint: Point): Vector3 {
        let lnglat = lnglatPoint.clone().toGeography();
        let longitude = MathUtils.degToRad(lnglat.x);
        let latitude = MathUtils.degToRad(lnglat.y);
        let height = lnglat.z;

        var wgs84RadiiSquared = new Vector3(
            6378137.0 * 6378137.0,
            6378137.0 * 6378137.0,
            6356752.3142451793 * 6356752.3142451793
        );

        var radiiSquared = wgs84RadiiSquared;
        var scratchN = new Vector3();
        var scratchK = new Vector3();

        var cosLatitude = Math.cos(latitude);
        scratchN.x = cosLatitude * Math.cos(longitude);
        scratchN.y = cosLatitude * Math.sin(longitude);
        scratchN.z = Math.sin(latitude);
        scratchN = scratchN.normalize();

        scratchK.x = radiiSquared.x * scratchN.x;
        scratchK.y = radiiSquared.y * scratchN.y;
        scratchK.z = radiiSquared.z * scratchN.z;

        var gamma = Math.sqrt(scratchN.dot(scratchK));
        scratchK = scratchK.divideScalar(gamma);
        scratchN = scratchN.multiplyScalar(height);

        var resultPoint: Vector3 = scratchK.add(scratchN);
        return resultPoint;
    }

    /** 地图容器像素坐标转为地图经纬度坐标 */
    public static containerToLngLat(pixel: Pixel): Point {
        let size = App.getInstance().getSize();
        let point = new Vector3().set((pixel.getX() / size.getWidth()) * 2 - 1, -(pixel.getY() / size.getHeight()) * 2 + 1, pixel.getZ());
        point.unproject(App.getInstance().three.camera);
        let lngLat = new Point(point.x, point.y, point.z, Const.EPSGType.EPSGWeb).toGeography();
        lngLat.z = 0;
        return lngLat;
    }

    public static haversineDistance(lat1, lon1, lat2, lon2) {
        // 将经纬度转换为弧度
        const toRadians = (angle) => angle * (Math.PI / 180);

        // 差值
        const dLat = toRadians(lat2 - lat1);
        const dLon = toRadians(lon2 - lon1);

        // Haversine 公式
        const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
            Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) *
            Math.sin(dLon / 2) * Math.sin(dLon / 2);

        const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

        // 地球平均半径（单位：千米）
        const radius = 6371 * 1000;

        // 计算距离
        const distance = radius * c;

        return distance;
    }

    public static getCoverPoint(scalar: number) { //获取被雾遮住的分割点
        let app = App.getInstance();
        let verticalVector = new Vector3(0, 0, -1 * app.three.camera.position.z);
        let verticalLength = verticalVector.length();
        let lookAtTarget = app.camera.lookAtTarget.clone();
        let cameraPosition = app.three.camera.position;
        let slopeVector = new Vector3(lookAtTarget.x - cameraPosition.x, lookAtTarget.y - cameraPosition.y, lookAtTarget.z - cameraPosition.z);
        let cosValue = verticalVector.clone().normalize().dot(slopeVector.clone().normalize());
        let slopeDistance = verticalLength / cosValue;

        let cameraPointOnGround = app.three.camera.position.clone();
        cameraPointOnGround.z = 0;
        let bottomVector = new Vector3(lookAtTarget.x - cameraPointOnGround.x, lookAtTarget.y - cameraPointOnGround.y, lookAtTarget.z - cameraPointOnGround.z).normalize();
        let bottomVectorDistance = Math.sqrt(slopeDistance * slopeDistance - verticalLength * verticalLength);
        bottomVector.multiplyScalar(bottomVectorDistance * scalar);
        cameraPointOnGround.add(bottomVector);

        // Project the 3D point onto the screen
        // cameraPointOnGround.project(app.three.camera);
        return cameraPointOnGround;
    }

    public static convertNdcToScreen(ndcPoint) {
        let app = App.getInstance();
        const screenX = (ndcPoint.x + 1) / 2 * app.getSize().getWidth();//.innerWidth;
        const screenY = (-ndcPoint.y + 1) / 2 * app.getSize().getHeight();//.window.innerHeight;
        return { x: screenX, y: screenY }
    }

    public static convertScreenToThree(screenX, screenY, ndcZ = -1.0) { //从屏幕坐标点转换到三维坐标点
        let app = App.getInstance();
        // Normalize the screen coordinates to the range [-1, 1]
        let normalizedX = (screenX / app.getSize().getWidth()) * 2 - 1;
        let normalizedY = -(screenY / app.getSize().getHeight()) * 2 + 1;

        // Create a 3D vector with the normalized screen coordinates
        let vector = new Vector3(normalizedX, normalizedY, ndcZ);

        vector.unproject(app.three.camera);
        return vector;
    }

    public static convertScreenToNdc(screenX, screenY, ndcZ = -1.0){
        let app = App.getInstance();
        // Normalize the screen coordinates to the range [-1, 1]
        let normalizedX = (screenX / app.getSize().getWidth()) * 2 - 1;
        let normalizedY = -(screenY / app.getSize().getHeight()) * 2 + 1;

        // Create a 3D vector with the normalized screen coordinates
        let vector = new Vector3(normalizedX, normalizedY, ndcZ);
        return vector;
    }

    public static convertScreenToCamera(screenX, screenY, ndcZ = -1.0) {
        let app = App.getInstance();
        // Normalize the screen coordinates to the range [-1, 1]
        let normalizedX = (screenX / app.getSize().getWidth()) * 2 - 1;
        let normalizedY = -(screenY / app.getSize().getHeight()) * 2 + 1;

        // Create a 3D vector with the normalized screen coordinates
        let vector = new Vector4(normalizedX, normalizedY, ndcZ, 1.0);
        vector.applyMatrix4(app.three.camera.projectionMatrixInverse);
        vector.x = vector.x / vector.w;
        vector.y = vector.y / vector.w;
        vector.z = vector.z / vector.w;

        let cameraPoint = new Vector3(vector.x, vector.y, vector.z);
        return cameraPoint;
    }

    public static getIntersetPoint(mapPlane: Plane, vector) {
        let app = App.getInstance();
        let ray: Ray = new Ray(app.three.camera.position.clone(), vector);
        let intersectionResult = new Vector3();
        intersectionResult = ray.intersectPlane(mapPlane, intersectionResult);
        return intersectionResult;
    }

    public static computeTileRange(pointArray, tileZoom?) {
        let zoom = tileZoom ? tileZoom : App.getInstance().map.zoom;
        let range = { minCol: Infinity, maxCol: -Infinity, minRow: Infinity, maxRow: -Infinity, zoom: zoom };
        for (let i = 0; i < pointArray.length; i++) {
            let currentPoint = pointArray[i];
            //这里为为了防止在计算瓦片的时候因为浮点精度问题造成瓦片计算错误，所以对坐标进行微调
            currentPoint.x = (currentPoint.x > 0) ? currentPoint.x + 0.1 : currentPoint.x - 0.1;
            currentPoint.y = (currentPoint.y > 0) ? currentPoint.y + 0.1 : currentPoint.y - 0.1;

            let col = GeoUtils.getC10(currentPoint.x, zoom);
            let row = GeoUtils.getR10(currentPoint.y, zoom);

            range.minCol = Math.min(range.minCol, col);
            range.maxCol = Math.max(range.maxCol, col);
            range.minRow = Math.min(range.minRow, row);
            range.maxRow = Math.max(range.maxRow, row);
        }
        
        let step = App.getInstance().options.Map.extraStep;
        let colStep = step;
        if(colStep > 0){
            colStep = colStep + 1; //鼠标横向拖动地图的时候，幅度一般会更大一些，为了避免新出现的区域没有加载出来，所以这里加1
        }
        range.minCol -= colStep;
        range.maxCol += colStep;

        let rowStep = step;
        range.minRow -= rowStep;
        range.maxRow += rowStep;
        return range;
    }
 
    public static getCurResolution(){
        let app = App.getInstance();
        let cameraHeight = app.camera.position.distanceTo(app.camera.lookAtTarget);
        
        let verticalFov = app.three.camera.fov;
        let horizontalFov = app.camera.cameraUtils.getHorizontalFov();

        let verticalFovSin = Math.sin( MathUtils.degToRad(verticalFov) );
        let verticalScope = cameraHeight * verticalFovSin;

        let horizontalFovSin = Math.sin( MathUtils.degToRad(horizontalFov) );
        let horizontalScope = cameraHeight * horizontalFovSin;

        let scopeWidth = Math.max(verticalScope, horizontalScope);

        let threeCanvasWidth = app.three.renderer.domElement.clientWidth;
        let threeCanvasHeight = app.three.renderer.domElement.clientHeight;

        let canvasWidth = Math.max(threeCanvasWidth, threeCanvasHeight);

        let resolution = scopeWidth / canvasWidth;
        return resolution;
    }
}

